/**
 * Copyright (c) 2010 Basil Shikin, BugStat Project
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
package org.bugstat.renderers.csv;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.bugstat.common.reports.DateKey;
import org.bugstat.common.reports.DoubleValue;
import org.bugstat.common.reports.IntegerValue;
import org.bugstat.common.reports.Report;
import org.bugstat.common.reports.ReportKey;
import org.bugstat.common.reports.ReportValue;
import org.bugstat.common.reports.ReportValueName;
import org.bugstat.common.reports.StringValueName;
import org.bugstat.common.reports.TupleIntegerValue;
import org.bugstat.common.reports.TupleValueName;
import org.bugstat.common.utils.ProgressListener;
import org.bugstat.renderers.Renderer;
import org.bugstat.renderers.RenderersString;
import org.bugstat.renderers.ReportInformation;
import org.bugstat.renderers.ReportRenderer;

/**
 * This class is used to render reports as CSV files.
 *
 * @author Basil Shikin
 *
 */
@Renderer
public class CSVReportRenderer
    implements ReportRenderer
{
    private static Log log = LogFactory.getLog( CSVReportRenderer.class );
    
    private static final char DELIM = ',';

    private static final String CSV_FOLDER = "csv";
    private static final String CSV_DATE_FORMAT = "yyyy-MM-dd";
    
    private static final char[] CSV_SPECIAL_CHARS = new char[]{ DELIM, '"', '\r', '\n' };
    
    protected DateFormat recordFileFormat = new SimpleDateFormat( CSV_DATE_FORMAT );

    
    /**
     * {@inheritDoc}
     */
    public Collection<File> renderReports(List<Report> reports, ReportInformation reportInformation, File folder, ProgressListener progressListener) throws IOException
    {
        if ( reports == null ) throw new IllegalArgumentException("Invalid reports specified");
        if ( folder == null ) throw new IllegalArgumentException("Invalid folder specified");
        if ( reportInformation == null ) throw new IllegalArgumentException("Invalid report information specified");
        if ( !folder.isDirectory() ) throw new IllegalArgumentException("Specified folder is not a folder: " + folder.getAbsolutePath() );
        
        final Collection<File> result = new HashSet<File>();
        
        int currentReport = 0;
        int totalReports = reports.size();
        
        for ( Report report : reports )
        {
            log.debug("Rendering CSV report for " + report );
            
            final File outputFolder = new File ( folder, CSV_FOLDER );
            if ( outputFolder.exists() ) outputFolder.delete();
            outputFolder.mkdirs();
            
            final File groupFolder = new File( outputFolder, report.getGroup() );
            groupFolder.mkdirs();
            
            final File outputFile = new File ( groupFolder, report.getName() + ".csv");
            outputFile.createNewFile();
            
            OutputStream out = null;
            
            try
            {
                out = new FileOutputStream( outputFile, false );
                
                writeReportHeader( out, report );
                
                for ( ReportKey key : report.getKeys() )
                {
                    writeReportEntry( out, key, report.getValueFor( key ) );
                }
            }
            finally
            {
                if ( out != null ) out.close();
            }
            
            log.debug("CSV report for " + report + " rendered into " + outputFile );
            
            currentReport += 1;
            if ( progressListener != null ) progressListener.progressReported( Math.round( 100F*(currentReport/totalReports) ) );
            
            result.add( outputFile );
        }
        
        return result;
    }
    
    public String getName()
    {
        return RenderersString.TEXT_CSV_RENDERER;
    }



    private void writeReportEntry(OutputStream out, ReportKey key, ReportValue value) throws IOException
    {
        if ( key instanceof DateKey )
        {
            final DateKey dateKey = (DateKey)key;
            out.write( recordFileFormat.format( dateKey.getDate() ).getBytes() );
        }
        else
        {
            out.write( '?' );
        }
        out.write( DELIM );
        
        if ( value instanceof IntegerValue )
        {
            final IntegerValue intValue = (IntegerValue)value;
            out.write( String.valueOf( intValue.getValue() ).getBytes() );
        }
        else if ( value instanceof DoubleValue )
        {
            final DoubleValue doubleValue = (DoubleValue)value;
            out.write( String.valueOf( doubleValue.getValue() ).getBytes() );
        }
        else if ( value instanceof TupleIntegerValue )
        {
            final TupleIntegerValue tupleValue = (TupleIntegerValue)value;
            
            for ( int i = 0; i < tupleValue.getLength(); i++ )
            {
                out.write( String.valueOf( tupleValue.getValue( i ) ).getBytes() );
                
                if ( i < tupleValue.getLength() - 1 ) out.write( DELIM );
            }
        }
        else
        {
            out.write( '?' );
        }
        out.write( '\n' );
    }

    private void writeReportHeader(OutputStream out, Report report) throws IOException
    {
        out.write( escapeValue( report.getKeyName() ).getBytes() );
        out.write( DELIM );
        final ReportValueName valueName = report.getValueName();
        
        if ( valueName instanceof StringValueName )
        {
            final StringValueName stringName = (StringValueName)valueName;
            
            out.write( escapeValue( stringName.getName() ).getBytes() );           
        }
        else if ( valueName instanceof TupleValueName )
        {
            final TupleValueName tupleName = (TupleValueName)valueName;
            
            for ( int i = 0; i < tupleName.getLength(); i++ )
            {
                out.write( escapeValue( tupleName.getName( i ) ).getBytes() );
                
                if ( i < tupleName.getLength() - 1 ) out.write( DELIM );
            }
        }
        else
        {
            out.write( '?' );
        }
        out.write( '\n' );
    }
    
    private String escapeValue( final String value )
    {
        if ( value == null || value.length() < 1  )
        {
            return "";
        }
        else if ( hasNoSpecialChars( value ) )
        {
            return value;
        }
        else
        {
            StringBuffer result = new StringBuffer( );
    
            result.append( '"' );
            for ( int i = 0; i < value.length( ); i++ )
            {
                char c = value.charAt( i );
                if ( c == '"' )
                {
                    result.append( '"' ); // quotes are double escaped
                }
                result.append( c );
            }
            result.append( '"' );
    
            return result.toString( );
        }
    }

    private boolean hasNoSpecialChars(String value)
    {
        for ( int i = 0; i < value.length(); i++ )
        {
            final char ch = value.charAt( i );
            for ( int j = 0; j < CSV_SPECIAL_CHARS.length; j++ )
            {
                if ( CSV_SPECIAL_CHARS[j] == ch )
                {
                    return false;
                }
            }
        }

        return true;
    }
}
